package com.cloud.mall.conf.gateway;

import cn.hutool.crypto.digest.MD5;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.cloud.mall.constants.CommonConstant;
import com.cloud.mall.enums.ExcEnum;
import com.cloud.mall.enums.HeaderEnum;
import com.cloud.mall.exp.CustomException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;


/**
 * @Author: 梁二东
 * @Date: 2024/11/24 11:49
 * @Description: 全局校验过滤器配置
 **/
@Component
@Slf4j
public class GlobalSignFilter implements GlobalFilter, Ordered {

    @Value("${cloud-mall.sign.salt}")
    private String signSalt;

    /**
     * default HttpMessageReader
     */
    private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        /**
         * 获取header中参与签名的参数
         */
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        String timestamp = headers.getFirst(HeaderEnum.timestamp.getHeaderName());
        String sign = headers.getFirst(HeaderEnum.sign.getHeaderName());
        // 获取url中参与签名的参数
        String urlParams = request.getURI().getQuery();
        Map<String, String> urlParamsMap = HttpUtil.decodeParamMap(urlParams, StandardCharsets.UTF_8);
        // 组合参与签名参数用于计算签名
        TreeMap<String, Object> signMap = new TreeMap<String, Object>() {
        };
        signMap.putAll(urlParamsMap);
        signMap.put(HeaderEnum.timestamp.getHeaderName(), timestamp);
        signMap.put(HeaderEnum.sign.getHeaderName(), sign);
        signMap.put(CommonConstant.SIGN_SALT, signSalt);

        // 获取body参数
        if (checkBodyHeader(request)) {
            return readBody(exchange, chain, signMap);
        } else {
            // 验证签名
            validSign(signMap);
        }
        return chain.filter(exchange);
    }


    /**
     * ReadJsonBody
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain, TreeMap<String, Object> signMap) {
        /**
         * join the body
         */
        return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
            byte[] bytes = new byte[dataBuffer.readableByteCount()];
            dataBuffer.read(bytes);
            DataBufferUtils.release(dataBuffer);
            Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                DataBufferUtils.retain(buffer);
                return Mono.just(buffer);
            });
            /**
             * repackage ServerHttpRequest
             */
            ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return cachedFlux;
                    /*return Flux.defer(() -> {
                        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                        DataBufferUtils.retain(buffer);
                        return Mono.just(buffer);
                    });*/
                }
            };
            /**
             */
            ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
            /**
             * read body string with default messageReaders
             */
            return ServerRequest.create(mutatedExchange, messageReaders).bodyToMono(String.class)
                    .doOnNext(objectValue -> {
                        log.info("[GatewayContext]Read Body:{}", objectValue);
                        Map<String, Object> requestBody = JSONUtil.toBean(objectValue, Map.class);
                        signMap.putAll(requestBody);
                        validSign(signMap);
                    }).then(chain.filter(mutatedExchange));
        });
    }


    @Override
    public int getOrder() {
        // HeaderGlobalFilter > GlobalSignFilter
        return HIGHEST_PRECEDENCE + 8;
    }

    /**
     * 校验body头
     *
     * @param req
     * @return
     */
    private static boolean checkBodyHeader(ServerHttpRequest req) {
        MediaType contentType = req.getHeaders().getContentType();
        long contentLength = req.getHeaders().getContentLength();
        if (contentLength > 0 && !MediaType.MULTIPART_FORM_DATA_VALUE.startsWith(contentType.toString())) {
            if (MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType) || contentType.toString().contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
                return true;
            }
        }
        return false;
    }


    /**
     * 验证签名，签名错误抛出异常
     *
     * @param signMap
     * @return
     */
    private static boolean validSign(TreeMap<String, Object> signMap) {
        String salt = signMap.remove(CommonConstant.SIGN_SALT).toString();
        String sign = signMap.remove(HeaderEnum.sign.getHeaderName()).toString();
        StringBuilder params = new StringBuilder();
        signMap.entrySet().forEach(entry -> {
            params.append(entry.getKey()).append(CommonConstant.COLON).append(entry.getValue()).append(CommonConstant.SEMICOLON);
        });
        params.append(CommonConstant.SIGN_SALT).append(CommonConstant.COLON).append(salt);
        log.info("gateway计算签名，参与签名的参数[{}]", params);
        String digestHex = MD5.create().digestHex(params.toString());
        log.info("请求入参sign[{}],gateway计算签名结果[{}]", sign, digestHex);
        if (!digestHex.equals(sign)) {
            throw CustomException.fail(ExcEnum.SIGN_ERROR);
        }
        return true;
    }
}
